package javax.realtime;

import java.util.BitSet;

public class RealtimeThread implements Schedulable {

	public enum RealtimeThreadState {
		RTT_NEW,
		RTT_READY,
		RTT_RUNNING,
		RTT_WAITING,
		RTT_TERMINATED;
	}

	private Thread thread;
	private RealtimeThreadState rttState;
	private SchedulingParameters scheduling;
	private ReleaseParameters release;
	//MemoryParameters memory;
	//ProcessingGroupParameters group;
	//MemoryArea area;
	private BitSet affinity;
	private int cpu;
	private int tid;
	private PeriodicTimer periodicTimer;
	private Scheduler scheduler = esrc.c9cf.schedulers.PartitionedPriorityScheduler.instance();
	
	private AbsoluteTime startTime = null;
	private AbsoluteTime nextReleaseTime = null;
	
	public RealtimeThread()
	{
		this(null,null,null,null,null,null);
	}
	public RealtimeThread(SchedulingParameters scheduling)
	{
		this(scheduling,null,null,null,null,null);
	}
	public RealtimeThread(SchedulingParameters scheduling, ReleaseParameters release)
	{
		this(scheduling,release,null,null,null,null);
	}
	public RealtimeThread(SchedulingParameters scheduling, ReleaseParameters release, BitSet affinity)
	{
		this(scheduling,release,null,null,null,null, affinity);
	}
	public RealtimeThread(SchedulingParameters scheduling, ReleaseParameters release, MemoryParameters memory, MemoryArea area, ProcessingGroupParameters group, Runnable logic)
	{
		this(scheduling,release,memory,area,group,logic,null);
	}
	public RealtimeThread(SchedulingParameters scheduling, ReleaseParameters release, MemoryParameters memory, MemoryArea area, ProcessingGroupParameters group, Runnable logic, BitSet affinity)
	{
		// TODO:memory and group parameters
		//super(logic);
		if(logic != null) throw new UnsupportedOperationException("Runnable 'logic' must be null for this implementation");
		thread = new Thread(this);
		rttState = RealtimeThreadState.RTT_NEW;
		//setScheduler(scheduler, scheduling, release, memory, group);
		this.scheduling = scheduling;
		this.release = release;
		if(release instanceof PeriodicParameters) periodicTimer = new PeriodicTimer(null, ((PeriodicParameters)release).getPeriod(), null, null, this);
		if(affinity == null) affinity = RealtimeSystem.availableProcessors(); 
		this.affinity = affinity;
		for(int i = 0; i < affinity.length(); i++)
		{
			if(affinity.get(i))
			{
				setCurrentCPU(i);
				break;
			}
		}
		//this.memory = memory;
		//if(area == null) area = MemoryArea.getMemoryArea(this);
		//this.area = area;
		//this.group = group;
	}
	public synchronized RealtimeThreadState getRealtimeThreadState()
	{
		return rttState;
	}
	public synchronized void setRealtimeThreadState(RealtimeThreadState state)
	{
		this.rttState = state;
	}
	public int getTID()
	{
		return tid;
	}
	public void setTID(int tid)
	{
		this.tid = tid;
	}
	@Override
	public boolean addIfFeasible() {
		if(scheduler != null)
		{
			if(scheduler instanceof esrc.c9cf.Scheduler)
			{
				((esrc.c9cf.Scheduler)scheduler).addIfFeasible(this);
			}
			else
			{
				scheduler.addToFeasibility(this);
				if(!scheduler.isFeasible())
				{
					scheduler.removeFromFeasibility(this);
					return false;
					}
				return true;
			}
		}
		return false;
	}

	@Override
	public boolean addToFeasibility() {
		if(scheduler != null) return scheduler.addToFeasibility(this);
		return false;
	}
/*
	@Override
	public MemoryParameters getMemoryParameters() {
		return memory;
	}

	@Override
	public ProcessingGroupParameters getProcessingGroupParameters() {
		return group;
	}
*/
	@Override
	public ReleaseParameters getReleaseParameters() {
		return release;
	}

	@Override
	public Scheduler getScheduler() {
		return scheduler;
	}

	@Override
	public SchedulingParameters getSchedulingParameters() {
		return scheduling;
	}

	@Override
	public boolean removeFromFeasibility() {
		if(scheduler != null) return scheduler.removeFromFeasibility(this);
		return false;
	}

	/*
	public void schedulePeriodic()
	{
		if(release instanceof PeriodicParameters)
		{
			...
		}
	}	
	
	
	@Override
	public boolean setIfFeasible(ReleaseParameters release,
			MemoryParameters memory) {
		// TODO:group parameters
		//return setIfFeasible(scheduling, release, memory, group);
		return setIfFeasible(scheduling, release, memory, null);
	}

	@Override
	public boolean setIfFeasible(ReleaseParameters release,
			MemoryParameters memory, ProcessingGroupParameters group) {
		return setIfFeasible(scheduling, release, memory, group);
	}

	@Override
	public boolean setIfFeasible(ReleaseParameters release,
			ProcessingGroupParameters group) {
		// TODO:memory parameters
		//return setIfFeasible(scheduling, release, memory, group);
		return setIfFeasible(scheduling, release, null, group);
	}

	@Override
	public boolean setIfFeasible(SchedulingParameters scheduling,
			ReleaseParameters release, MemoryParameters memory) {
		// TODO:group parameters
		//return setIfFeasible(scheduling, release, memory, group);
		return setIfFeasible(scheduling, release, memory, null);
	}

	@Override
	public boolean setIfFeasible(SchedulingParameters scheduling,
			ReleaseParameters release, MemoryParameters memory,
			ProcessingGroupParameters group) {
		return setSchedulingParametersIfFeasible(scheduling) && setReleaseParametersIfFeasible(release) && setMemoryParametersIfFeasible(memory) && setProcessingGroupParametersIfFeasible(group); 
	}


	@Override
	public void setMemoryParameters(MemoryParameters memory) {
		// TODO Auto-generated method stub
		if(memory == null) memory = getScheduler().getDefaultMemoryParameters();
	}

	@Override
	public boolean setMemoryParametersIfFeasible(MemoryParameters memory) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public void setProcessingGroupParameters(ProcessingGroupParameters group) {
		// TODO Auto-generated method stub

	}

	@Override
	public boolean setProcessingGroupParametersIfFeasible(
			ProcessingGroupParameters group) {
		// TODO Auto-generated method stub
		return false;
	}
*/
	@Override
	public void setReleaseParameters(ReleaseParameters release) {
		this.release = release;
	}

	@Override
	public boolean setReleaseParametersIfFeasible(ReleaseParameters release) {
		if(scheduler instanceof esrc.c9cf.Scheduler) return scheduler.setIfFeasible(this, release);
		setReleaseParameters(release);
		return true;
	}

	@Override
	public void setScheduler(Scheduler scheduler) {
		if(scheduler == null) throw new IllegalArgumentException();
		//RealtimeSystem.getSecurityManager().checkSetScheduler();
		if(scheduler instanceof esrc.c9cf.Scheduler)
		{
			if(this.scheduler instanceof esrc.c9cf.Scheduler) ((esrc.c9cf.Scheduler) this.scheduler).removeFromFeasibilitySet(this); 
			((esrc.c9cf.Scheduler)scheduler).addToFeasibilitySet(this);
		}
		this.scheduler = scheduler;
	}
/*
	@Override
	public void setScheduler(Scheduler scheduler,
			SchedulingParameters scheduling, ReleaseParameters release,
			MemoryParameters memory, ProcessingGroupParameters group) {
		setScheduler(scheduler);
		setSchedulingParameters(scheduling);
		setReleaseParameters(release);
		setMemoryParameters(memory);
		setProcessingGroupParameters(group);
	}
*/
	@Override
	public void setSchedulingParameters(SchedulingParameters scheduling)
	{
		this.scheduling = scheduling;
	}

	@Override
	public boolean setSchedulingParametersIfFeasible(SchedulingParameters scheduling)
	{
		if(scheduler instanceof esrc.c9cf.Scheduler) return scheduler.setIfFeasible(this, scheduling, release);
		setSchedulingParameters(scheduling);
		return true;
	}
	
	public static void sleep(Clock clock, HighResolutionTime time) throws InterruptedException
	{
		if(time == null) throw new IllegalArgumentException();
		if(clock == null) clock = Clock.getRealtimeClock();
		RelativeTime delta = null;
		if(time instanceof AbsoluteTime) delta = ((AbsoluteTime)time).subtract(clock.getTime());
		else if(time instanceof RelativeTime) delta = (RelativeTime)time;
		else delta = new RelativeTime(0,0);
		NativeHelper.nanosleep(delta.milliseconds, delta.nanoseconds);
	}
	public static void sleep(HighResolutionTime time) throws InterruptedException
	{
		sleep(time.getClock(), time);
	}
	public void start()
	{
		setRealtimeThreadState(RealtimeThreadState.RTT_NEW);
		startTime = release.getDeadline().getClock().getTime();
		if(release instanceof PeriodicParameters)
		{
			PeriodicParameters periodic = (PeriodicParameters)release;
			if(periodic.getStart() instanceof AbsoluteTime) startTime = ((AbsoluteTime)periodic.getStart());
			else if(periodic.getStart() instanceof RelativeTime) startTime = startTime.add(((RelativeTime)periodic.getStart()));
			if(scheduler instanceof esrc.c9cf.Scheduler)
			{
				nextReleaseTime = startTime;
				if(startTime.compareTo(startTime.getClock().getTime()) > 0)
				{
					//reserve a OneShotTimer, but we don't have this one for now...
				}
				//((esrc.c9cf.Scheduler)scheduler).reschedulePeriodic(this);
				//setNextReleaseTime();
				thread.start();
				periodicTimer.start();
			}
		}
	}
	public static boolean waitForNextPeriod()
	{
		boolean rtn = false;
		try {
			rtn = waitForNextPeriodInterruptible();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return rtn;
		/*
		RealtimeThread t = this;
		setNextStartTime();
		if(t instanceof RealtimeThread)
		{
			RealtimeThread rtt = (RealtimeThread)t;
			rtt.setRealtimeThreadState(RealtimeThreadState.RTT_WAITING);
			ReleaseParameters release = rtt.getReleaseParameters();
			AbsoluteTime end = release.getDeadline().getClock().getTime();
			boolean rtn = true;
			
			RelativeTime completionTime = end.subtract(rtt.startTime);
			// deadline miss?
			if(release.getDeadline().compareTo(completionTime) < 0)
			{
				rtn = false;
				if(release.getDeadlineMissHandler() != null) rtt.scheduler.fireSchedulable(release.getDeadlineMissHandler());
			}
			// cost overrun?
			if(release.getCost().compareTo(completionTime) < 0)
			{
				if(release.getCostOverrunHandler() != null) rtt.scheduler.fireSchedulable(release.getCostOverrunHandler());
			}
			
			if(release instanceof PeriodicParameters)
			{
				PeriodicParameters periodic = (PeriodicParameters)release;
				//rtt.startTime = rtt.startTime.add(periodic.getPeriod());

				if(rtt.scheduler instanceof esrc.c9cf.Scheduler)
				{
					//System.out.println("Reschedule from RT.waitForNextPeriod()");
					//((esrc.c9cf.Scheduler)rtt.scheduler).reschedule();
					// sleep
					synchronized(rtt)
					{
						try {
							rtt.wait();
						} catch (InterruptedException e) {
						}
					}
					rtt.setRealtimeThreadState(RealtimeThreadState.RTT_RUNNING);
					rtt.startTime = rtt.nextStartTime;
					return rtn;
				}
				else
				{
					/*
					RelativeTime period = periodic.getPeriod();
					RelativeTime delta = rtt.startTimeRTC.add(periodic.deadline.milliseconds, periodic.deadline.nanoseconds);
					boolean rtn = true;
					if(Clock.getRealtimeClock().getTime().compareTo() > 0) rtn = false;
					sleep(time);					
					return rtn;
					*/
		/*
					return rtn;
				}
			} else throw new IllegalThreadStateException();
		} else throw new ClassCastException("" + t.getClass().toString());
		*/
	}
	public static boolean waitForNextPeriodInterruptible() throws InterruptedException
	{
		boolean rtn = true;
		java.lang.Thread t = Thread.currentThread();
		Runnable runnable = ((Thread)t).getRunnable();
		RealtimeThread rtt = (RealtimeThread)runnable;
				rtt.setNextReleaseTime();
				rtt.setRealtimeThreadState(RealtimeThreadState.RTT_WAITING);
				ReleaseParameters release = rtt.getReleaseParameters();
				AbsoluteTime end = release.getDeadline().getClock().getTime();
				RelativeTime completionTime = end.subtract(rtt.startTime);
				// deadline miss?
				if(release.getDeadline().compareTo(completionTime) < 0)
				{
					rtn = false;
					if(release.getDeadlineMissHandler() != null) rtt.scheduler.fireSchedulable(release.getDeadlineMissHandler());
					//throw new RuntimeException("Deadline miss! tid=" + rtt.getTID());
					System.out.println("Deadline miss " + rtt.getTID() + ", expected " + release.getDeadline() + ", actual " + completionTime);
				}
				// cost overrun?
				if(release.getCost().compareTo(completionTime) < 0)
				{
					if(release.getCostOverrunHandler() != null) rtt.scheduler.fireSchedulable(release.getCostOverrunHandler());
				}
				if(release instanceof PeriodicParameters)
				{
					if(rtt.scheduler instanceof esrc.c9cf.Scheduler)
					{
						((esrc.c9cf.Scheduler)rtt.scheduler).reschedule(release.getDeadline().getClock().getTime(), rtt);
						Thread thread = rtt.getThread();
						synchronized(thread)
						{
							try {
								thread.wait();
							} catch (InterruptedException e) {}
						}
						rtt.setRealtimeThreadState(RealtimeThreadState.RTT_RUNNING);
						rtt.startTime = rtt.nextReleaseTime;
						return rtn;
					}
					return rtn;
				} else throw new IllegalThreadStateException();
	}
	public BitSet setAffinity(BitSet affinity)
	{
		if(affinity == null || affinity.length() != RealtimeSystem.availableProcessors().length()) throw new IllegalArgumentException();
		BitSet old_affinity = this.affinity;
		this.affinity = affinity;
		return old_affinity;
	}
	public BitSet getAffinity()
	{
		return affinity;
	}
	public boolean setCurrentCPU(int cpu)
	{
		if(cpu < 0 || cpu >= affinity.length() || !affinity.get(cpu)) throw new IllegalArgumentException();
		this.cpu = cpu;
		return true;
	}
	public int getCurrentCPU()
	{
		return cpu;
	}
	public AbsoluteTime getStartTime()
	{
		return startTime;
	}
	public AbsoluteTime getNextReleaseTime()
	{
		return nextReleaseTime;
	}
	public void stopTimer()
	{
		if(periodicTimer != null)
		{
			periodicTimer.stop();
		}
	}
	public void setNextReleaseTime()
	{
		if(release instanceof PeriodicParameters)
		{
			nextReleaseTime = nextReleaseTime.add(((PeriodicParameters)release).getPeriod());
		}
	}
	public Thread getThread()
	{
		return thread;
	}
	public void join() throws InterruptedException
	{
		thread.join();
	}
	@Override
	public void run() {
	}
}
